#include "ql.h"  
#include <ql\quantlib.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/unordered_map.hpp>
#include <boost/algorithm/string.hpp>
#include <vector>
#include <sstream>

using boost::assign::map_list_of;
using namespace QuantLib;
using namespace std;

map<std::string, boost::shared_ptr<QuantLib::PricingEngine>> pricingEngines;
map<std::string, boost::shared_ptr<QuantLib::YieldTermStructure>> yieldCurves;
map<std::string, RelinkableHandle<QuantLib::YieldTermStructure>> relinkableHandles;
map<std::string, boost::shared_ptr<QuantLib::VanillaSwap>> trades; 

ql::ql(void){}  
ql::~ql(void){}  

//static
std::vector<std::string> stringToVector(std::string s1)
{
		std::vector<std::string> v;
		boost::split(v, s1, boost::is_any_of(","), boost::token_compress_on);
		return v;
}

std::vector<std::vector<std::string>> stringTo2DVector(std::string s1)
{
		std::vector<std::string> v1;
		std::vector<std::vector<std::string>> v2;
		boost::split(v1, s1, boost::is_any_of(";"), boost::token_compress_on);
	    for each(std::string s2 in v1)
		{ 
			std::vector<std::string> v;
			boost::split(v, s2, boost::is_any_of(","), boost::token_compress_on);
			v2.push_back(v);
		}
		return v2;
}

QuantLib::DayCounter DayCount(std::string dayCount)
{
	if(dayCount == "Euro30/360")
		return Thirty360(Thirty360::European); 
	else if(dayCount == "Act/360")
		return Actual360(); 
	else
		return Actual360(); 
}

QuantLib::Frequency Freq(std::string freq)
{
	if(freq == "Monthly")
		return Monthly; 
	else if(freq == "Quarterly")
		return Quarterly; 
	else if(freq == "Semiannual")
		return Semiannual; 
	else if(freq == "Annual")
		return Annual; 
	else
		return OtherFrequency; 
}

VanillaSwap::Type PayRec(std::string payRec)
{
	if(payRec == "Receiver")
		return VanillaSwap::Receiver; 
	else if(payRec == "Payer")
		return VanillaSwap::Payer;
	else
		return VanillaSwap::Payer;
}

QuantLib::BusinessDayConvention BizConv(std::string bizConv)
{
	if(bizConv == "Following")
		return Following; 
	else if(bizConv == "ModifiedFollowing")
		return ModifiedFollowing;
	else if(bizConv == "ModifiedPreceding")
		return ModifiedPreceding;
	else if(bizConv == "Preceding")
		return Preceding;
	else if(bizConv == "Unadjusted")
		return Unadjusted;
	else
		return Unadjusted;
}

QuantLib::Calendar Holidays(std::string holidays)
{
	return TARGET();
}

//class
std::swigtest(std::string val)
{
	return val
}

std::string ql::EngineLink(std::string engineName, std::string discountCurveName, std::string forecastCurveName)
{
	try
	{
		boost::shared_ptr<YieldTermStructure> discCurve = yieldCurves[discountCurveName];
		boost::shared_ptr<YieldTermStructure> forcCurve = yieldCurves[forecastCurveName];

		//something for linking need to create some sort of market helper
		RelinkableHandle<YieldTermStructure> discountCurve;
		RelinkableHandle<YieldTermStructure> forecastCurve;
		relinkableHandles[discountCurveName] =  discountCurve;
		relinkableHandles[forecastCurveName] =  forecastCurve;

		boost::shared_ptr<PricingEngine> swapEngine(new DiscountingSwapEngine(discountCurve));
		pricingEngines[engineName] = swapEngine;
		discountCurve.linkTo(discCurve);
		forecastCurve.linkTo(forcCurve);

		return engineName;
	} catch (std::exception& e) {return e.what();
	} catch (...) { return "unknown error";
	}
}

std::string ql::EngineValueTrade(std::string engineName, std::string tradeId, std::string controlString)
{
	try
	{
		
		
		VanillaSwap trade = *trades[tradeId].get();
		trade.setPricingEngine(pricingEngines[engineName]);
		std::vector<std::string> v = stringToVector(controlString);

		std::stringstream res;
		res << "tradeId," << controlString << ";" << tradeId;

		for each(std::string s in v)
		{
			res << ",";
			if(s == "NPV")
				res << trade.NPV();
			else if(s == "fairSpread")
				res << trade.fairSpread();
			else if(s == "fairRate")
				res << trade.fairRate();
			else
				res << "unknown";
		}

		return res.str();

	} catch (std::exception& e) {return e.what();
	} catch (...) { return "unknown error";
	}
}

std::string ql::YieldCurve(std::string name, int settlementDate, int fixingDays, std::string levels, std::string holidays)
{
	try 
	{
	    vector<std::vector<std::string>> levelsVector = stringTo2DVector(levels);

		boost::shared_ptr<YieldTermStructure> yieldTermStructure;

		Date settleDate(settlementDate);
        Integer fixDays = fixingDays;
        Calendar calendar = Holidays(holidays);
        settleDate = calendar.adjust(settleDate);
		DayCounter depositDayCounter = Actual360();
		DayCounter termStructureDayCounter = ActualActual(ActualActual::ISDA);

		Date todaysDate = calendar.advance(settleDate, -fixingDays, Days);
		Settings::instance().evaluationDate() = todaysDate;
		todaysDate = Settings::instance().evaluationDate();
			
		//setup fut 
		Date imm = IMM::nextDate(settleDate);
		Integer futMonths = 3;
		
		// setup swaps
		Frequency swFixedLegFrequency = Annual;
		BusinessDayConvention swFixedLegConvention = Unadjusted;
		DayCounter swFixedLegDayCounter = Thirty360(Thirty360::European);
		boost::shared_ptr<IborIndex> swFloatingLegIndex(new Euribor6M);
		
		double tolerance = 1.0e-15;
		const boost::unordered_map<std::string, TimeUnit> tu = map_list_of("Weeks", Weeks)("Months", Months)("Years", Years);
		//const std::string depo = "Depo", fra = "FRA", fut = "Fut", swap = "Swap";

		std::vector<boost::shared_ptr<RateHelper>> instruments;

		for each(std::vector<std::string> v in levelsVector)
		{ 
			if(v[0] == "Depo")
			{
				Rate depoRate = std::stof(v[4]);
				boost::shared_ptr<Quote> depoQuote(new SimpleQuote(depoRate));
				boost::shared_ptr<RateHelper> depo(new DepositRateHelper(Handle<Quote>(depoQuote), std::stoi(v[2])*tu.at(v[3]), fixingDays, calendar, ModifiedFollowing, true, depositDayCounter));
				instruments.push_back(depo);
			}
			else if(v[0] == "FRA")
			{
				Rate fraRate = std::stof(v[4]);
				boost::shared_ptr<Quote> fraQuote(new SimpleQuote(fraRate));
				boost::shared_ptr<RateHelper> fra(new FraRateHelper(Handle<Quote>(fraQuote), std::stoi(v[2]), std::stoi(v[3]), fixingDays, calendar, ModifiedFollowing, true, depositDayCounter));
				instruments.push_back(fra);
			}
			else if(v[0] == "Fut")
			{
				Real futReal = std::stof(v[4]);
				boost::shared_ptr<Quote> futQuote(new SimpleQuote(futReal));
				boost::shared_ptr<RateHelper> fut(new FuturesRateHelper(Handle<Quote>(futQuote), imm, futMonths, calendar, ModifiedFollowing, true, depositDayCounter));
				imm = IMM::nextDate(imm+1);
				instruments.push_back(fut);
			}
			else if(v[0] == "Swap")
			{
				Rate swapRate = std::stof(v[4]);
				boost::shared_ptr<Quote> swapQuote(new SimpleQuote(swapRate));
				boost::shared_ptr<RateHelper> swap(new SwapRateHelper(Handle<Quote>(swapQuote), std::stoi(v[2])*tu.at(v[3]), calendar, swFixedLegFrequency, swFixedLegConvention, swFixedLegDayCounter, swFloatingLegIndex));
				instruments.push_back(swap);
			}
		}

		yieldCurves[name] = boost::shared_ptr<YieldTermStructure>(new PiecewiseYieldCurve<Discount,LogLinear>(settleDate, instruments, termStructureDayCounter, tolerance));
		
		return name; 

	} catch (std::exception& e) {return e.what();
	} catch (...) { return "unknown error" ;
	}
}

std::string ql::VanillaSwapTrade(std::string tradeId, double nominal, int start, int maturity, double fixedRate, std::string payRec, std::string index, double spread, std::string  fixLegFrq, std::string fltLegFrq, std::string fixLegConv,  std::string fltLegConv, std::string fixLegDayCount, std::string fltLegDayCount, std::string holidays)
{
	boost::shared_ptr<VanillaSwap> vanillaSwap;
	try 
	{ 
		boost::shared_ptr<IborIndex> index(new Euribor6M(relinkableHandles[index]));
	
		Calendar calendar = Holidays(holidays);
		Date startDate(start);
		Date maturityDate(maturity);

		Schedule fixedSchedule(startDate, maturityDate, Period(Freq(fixLegFrq)), calendar, BizConv(fixLegConv), BizConv(fixLegConv), DateGeneration::Forward, false);
		Schedule floatSchedule(startDate, maturityDate, Period(Freq(fltLegFrq)), calendar, BizConv(fltLegConv), BizConv(fltLegConv), DateGeneration::Forward, false);
		trades[tradeId] = boost::shared_ptr<VanillaSwap>(new VanillaSwap(PayRec(payRec), nominal, fixedSchedule, fixedRate, DayCount(fixLegDayCount), floatSchedule, index, spread, DayCount(fltLegDayCount)));
		
		return tradeId;
	} catch (std::exception& e) {return e.what();
	} catch (...) { return "unknown error";
	}
}